#include <string.h>
#include <stdbool.h>
#include <linux/limits.h>
#include "enclave_log.h"
#include "msg_structure.h"

#define SHIFT8      (8)
#define SHIFT24     (24)
uint32_t swap32(uint32_t num)
{
    return ((((num) & 0xFF000000) >> SHIFT24) | (((num) & 0x00FF0000) >> SHIFT8) \
           | (((num) & 0x0000FF00) << SHIFT8) | (((num) & 0x000000FF) << SHIFT24));
}

#define MOV_PTR(ptr, add)  (ptr) += (add)

inline static bool is_bigedian(void)
{
    uint32_t test = 0x11223344;
    uint8_t *ptr = (uint8_t *)&test;
    return *ptr == 0x11? false : true;
}

static int32_t fill_uint32(uint8_t *buf, size_t buf_size, uint32_t data)
{
    if (buf_size < sizeof(uint32_t)) {
        return -1;
    }
    if (!is_bigedian()) {
        data = swap32(data);
    }
    memcpy(buf, &data, sizeof(data));
    return sizeof(uint32_t);
}

static int32_t get_uint32(uint32_t *data, uint8_t *buf, size_t buf_size)
{
    if (buf_size < sizeof(uint32_t)) {
        return -1;
    }
    memcpy((uint8_t*)data, buf, sizeof(uint32_t));
    if (!is_bigedian()) {
        *data = swap32(*data);
    }
    return sizeof(uint32_t);
}

static int32_t fill_mem(uint8_t *buf, size_t buf_size, const uint8_t *src, size_t src_size)
{
    if (buf_size < src_size) {
        print_debug("short buffer, buffer size:%d, filled %d\n", buf_size, src_size);
        return -1;
    }
    print_debug("fill %p size %zu, from %p size %zu\n", buf, buf_size, src, src_size);
    memcpy(buf, src, src_size);
    return src_size;
}

int32_t pack_msg(uint8_t* buf, size_t buf_size, const msg_t *msg)
{
    uint8_t *offset = buf;
    uint8_t *end = buf + buf_size;
    /* send buf format length(MSG_LENGTH_SIZE) + OUT_BUF_LENGTH_SIZE + function id(FUNCTION_ID_SIZE) + data */
    // reserve for total length
    MOV_PTR(offset, MSG_LENGTH_SIZE);

    // filled out buffer size
    if (fill_uint32(offset, end - offset, msg->out_buf_size) < 0) {
        print_debug("fill output buffer size fail\n");
        return ERR_MSG_GENERIC;
    }
    MOV_PTR(offset, OUT_BUF_LENGTH_SIZE);

    // filled functionid
    if (fill_uint32(offset, end - offset, msg->function_id) < 0) {
        print_debug("get function id fail\n");
        return ERR_MSG_GENERIC;
    }
    MOV_PTR(offset, FUNCTION_ID_SIZE);

    // filled data
    if (fill_mem(offset, end - offset, msg->buf, msg->buf_size) < 0) {
        print_debug("fill data fail\n");
        return ERR_MSG_GENERIC;
    }
    MOV_PTR(offset, msg->buf_size);

    // filled total length
    uint32_t msg_len = offset - buf;
    if (fill_uint32(buf, MSG_LENGTH_SIZE, msg_len) < 0) {
        print_debug("fill message length fail\n");
        return ERR_MSG_GENERIC;
    }
    return msg_len;
}

// attention：buf in msg_t from heap，need free
int32_t unpack_msg(msg_t *msg, const uint8_t *buf, size_t buf_size)
{
    uint8_t *offset = (uint8_t *)buf;
    uint32_t msg_len = 0;
    if (buf_size < MSG_HEADER_SIZE) {
        return ERR_MSG_GENERIC;
    }
    if (get_uint32(&msg_len, offset, MSG_LENGTH_SIZE) < 0) {
        print_debug("get message length in header fail\n");
        return ERR_MSG_GENERIC;
    }
    if (msg_len != buf_size) {
        print_debug("message length not match: expect size %u, input size %zu\n", msg_len, buf_size);
        return ERR_MSG_SIZE;
    }
    MOV_PTR(offset, MSG_LENGTH_SIZE);
    msg_len -= MSG_LENGTH_SIZE;
    uint32_t data32 = 0;
    if (get_uint32((uint32_t*)&(data32), offset, OUT_BUF_LENGTH_SIZE) < 0) {
        print_debug("get output buffer length fail\n");
        return ERR_MSG_GENERIC;
    }
    msg->out_buf_size  = data32;
    MOV_PTR(offset, OUT_BUF_LENGTH_SIZE);
    msg_len -= OUT_BUF_LENGTH_SIZE;

    if (get_uint32((uint32_t*)&(data32), offset, FUNCTION_ID_SIZE) < 0) {
        print_debug("get message id fail\n");
        return ERR_MSG_GENERIC;
    }
    msg->function_id = data32;
    MOV_PTR(offset, FUNCTION_ID_SIZE);
    msg_len -= FUNCTION_ID_SIZE;

    if (msg_len > 0) {
        msg->buf = calloc(1, msg_len);
        if (msg->buf == NULL) {
            return ERR_MSG_GENERIC;
        }
        msg->buf_size = msg_len;
    }
    if (fill_mem(msg->buf, msg->buf_size, offset, msg_len) < 0) {
        print_debug("fill buffer fail\n");
        return ERR_MSG_GENERIC;
    }
    return ERR_MSG_OK;
}